using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Diagnostics;
using System.Net;
using System.Web.Services.Protocols;
using Microsoft.SharePoint;
using PSLibrary = Microsoft.Office.Project.Server.Library;
using System.Threading;


namespace MicrosoftFrance.MCS.EPM2007
{
    static class Program
    {
        #region Initialize PSI WebServices
        public static AdminWS.Admin admin = new AdminWS.Admin();
        public static CalendarWS.Calendar calendar = new CalendarWS.Calendar();
        public static CustomFieldsWS.CustomFields customfields = new CustomFieldsWS.CustomFields();
        public static LoginFormsWS.LoginForms loginForms = new LoginFormsWS.LoginForms();
        public static LoginWindowsWS.LoginWindows loginWindows = new LoginWindowsWS.LoginWindows();
        public static LookupTableWS.LookupTable lookuptable = new LookupTableWS.LookupTable();
        public static ProjectWS.Project project = new ProjectWS.Project();
        public static QueueSystemWS.QueueSystem queueSystem = new QueueSystemWS.QueueSystem();
        public static ResourceWS.Resource resource = new ResourceWS.Resource();
        public static StatusingWS.Statusing statusing = new StatusingWS.Statusing();
        public static StatusingDerived statusingDER = new StatusingDerived();
        public static TimeSheetWS.TimeSheet timeSheet = new TimeSheetWS.TimeSheet();
        #endregion

        /// <summary>
        /// Automate TimeSheet Creation and Statusing
        /// </summary>
        /// <param name="args"></param>
        [STAThread]
        static void Main(string[] args)
        {
            #region Parameters

            string projectServerURL = string.Empty; // PWA URL
            string connectionString = string.Empty; // Published database connection string
            bool useEarliestPeriod = true; // <-- Use first TS period if true, else use TS from today onward
            string periodName = "*"; // <-- Create TS for a specific TS period, set to "*" if you want to ignore
            int nbTSToCreate = 1; // <-- TS per resource
            int nbResource = 1; // <-- Number of resource TS to create
            int nbPM = 1; // <-- Number of resource TS to create
            DateTime startDate = new DateTime(2006, 1, 1); // Start date range to purge TS
            DateTime finishDate = startDate.AddYears(3); // Finish date range to purge TS
            string resourceName = string.Empty; // Default value
            string timesheetManagerName = string.Empty; // Default value
            string projectManagerName = string.Empty; // Default value
            bool purgeAllTSForAllRes = false; // Purge all TS for all resources for all periods
            bool waitForQueue = false; // If true processing time will increase significantly
            bool detailLog = false; // Set to true to get a detail log of updates
            bool createTS = false; // Set to true to create a TS/Modify/Submit/Approve
            bool approveTS = false; // Set to true to approve the created, modified and submited TS
            bool importTS = false; // Set to true to import TS and do statusing
            bool approveTaskUpdate = false; // Set to true to approve all task update
            bool debugDefaultRes = false; // If set to true only the default resource will be used
            bool debugDefaultPM = false; // If set to true only the default project manager will be used
            bool resetCFForAllRes = false; // Reset specific resource CF flag to false
            string resourceCFTS = string.Empty; // Resource custom field of type flag used to track resources that have been updated
            string resourceCFST = string.Empty; // Resource custom field of type flag used to track resources that have been updated
            long waitTimesheets = 0; // Waiting time after 1 resource submit 1 timesheet and it is approved
            long waitTasks = 0; // Waiting time after 1 resource submit his updates in "My Tasks"
            long waitApprovals = 0; // Waiting time after 1 project manager approves all his pending updates
            // Ignore if not doing statusing
            Guid siteID = Guid.Empty; // Used for impersonation (in WSS Content db: SELECT SiteId FROM WEBS WHERE FullUrl = 'PWAName')
            string sspName = string.Empty; // SSP Name used for impersonation (-> check in IIS)
            int sspPort = 0; // SSP Port Number used for impersonation (-> check in IIS)
            string summaryFileLog = "AutomateTS_Summary.log"; // Summary file log with all results
            bool purgeAllTSForOneRes = false; // Purge all TS for the default resource (resourceName).
            int taskRemaingHoursBag = 1920000; // Bucket of hours spread randomly accross all task
            bool randomizeTSStatus = false; // Randomize timesheet status

            #endregion

            #region Read parameters from command line and app.config

            // Read app.config arguements
            foreach (string key in ConfigurationManager.AppSettings)
            {
                switch (key.Trim().ToLower())
                {
                    case "pwaurl":
                        projectServerURL = ConfigurationManager.AppSettings[key];
                        break;
                    case "pubdbconnectionstring":
                        connectionString = ConfigurationManager.AppSettings[key];
                        break;
                    case "defaultresourcename":
                        resourceName = ConfigurationManager.AppSettings[key];
                        break;
                    case "timesheetmanagername":
                        timesheetManagerName = ConfigurationManager.AppSettings[key];
                        break;
                    case "defaultprojectmanagername":
                        projectManagerName = ConfigurationManager.AppSettings[key];
                        break;
                    case "resourcecftimesheet":
                        resourceCFTS = ConfigurationManager.AppSettings[key];
                        break;
                    case "resourcecfstatusing":
                        resourceCFST = ConfigurationManager.AppSettings[key];
                        break;
                    case "waitforqueue":
                        waitForQueue = bool.Parse(ConfigurationManager.AppSettings[key]);
                        break;
                    case "detaillog":
                        detailLog = bool.Parse(ConfigurationManager.AppSettings[key]);
                        break;
                    case "siteid":
                        siteID = new Guid(ConfigurationManager.AppSettings[key]);
                        break;
                    case "sspname":
                        sspName = ConfigurationManager.AppSettings[key];
                        break;
                    case "sspport":
                        sspPort = Int32.Parse(ConfigurationManager.AppSettings[key]);
                        break;
                    case "summaryfilelog":
                        summaryFileLog = ConfigurationManager.AppSettings[key];
                        break;
                }
            }
            
            // Read command line arguments
            for (int a = 0; a < args.Length; a++)
            {
                switch (args[a].Trim().ToLower())
                {
                    case "/rn":
                        if (a < args.Length - 1)
                        {
                            resourceName = args[++a];
                        }
                        break;
                    case "/tmn":
                        if (a < args.Length - 1)
                        {
                            timesheetManagerName = args[++a];
                        }
                        break;
                    case "/pmn":
                        if (a < args.Length - 1)
                        {
                            projectManagerName = args[++a];
                        }
                        break;
                    case "/p":
                        if (a < args.Length - 1)
                        {
                            periodName = args[a] != null ? args[++a] : "*";
                        }
                        break;
                    case "/r":
                        if (a < args.Length - 1)
                        {
                            nbResource = Int32.Parse(args[++a]);
                        }
                        break;
                    case "/pm":
                        if (a < args.Length - 1)
                        {
                            nbPM = Int32.Parse(args[++a]);
                        }
                        break;
                    case "/t":
                        if (a < args.Length - 1)
                        {
                            nbTSToCreate = Int32.Parse(args[++a]);
                        }
                        break;
                    case "/wat":
                        if (a < args.Length - 1)
                        {
                            waitTimesheets = Int64.Parse(args[++a]);
                        }
                        break;
                    case "/was":
                        if (a < args.Length - 1)
                        {
                            waitTasks = Int64.Parse(args[++a]);
                        }
                        break;
                    case "/waa":
                        if (a < args.Length - 1)
                        {
                            waitApprovals = Int64.Parse(args[++a]);
                        }
                        break;
                    case "/ct":
                        createTS = true;
                        break;
                    case "/at":
                        approveTS = true;
                        break;
                    case "/it":
                        importTS = true;
                        break;
                    case "/as":
                        approveTaskUpdate = true;
                        break;
                    case "/pts":
                        purgeAllTSForAllRes = true;
                        break;
                    case "/rcf":
                        resetCFForAllRes = true;
                        break;
                    case "/dr":
                        debugDefaultRes = true;
                        break;
                    case "/dpm":
                        debugDefaultPM = true;
                        break;
                    case "/sd":
                        if (a < args.Length - 1)
                        {
                            startDate = DateTime.ParseExact(args[++a], "yyyyMMdd", null);
                        }
                        break;
                    case "/fd":
                        if (a < args.Length - 1)
                        {
                            finishDate = DateTime.ParseExact(args[++a], "yyyyMMdd", null);
                        }
                        break;
                    case "/trhb":
                       if (a < args.Length - 1)
                        {
                            taskRemaingHoursBag = Int32.Parse(args[++a]);
                        }
                        break;

                    case "/rtsst":
                        randomizeTSStatus = true;
                        break;

                    case "/?":
                    case "/h":
                        WriteCommandLineHelp();
                        Environment.Exit(0);
                        break;
                    default:
                        Console.WriteLine("Encountered unexpected option \"" + args[a] + "\".");
                        WriteCommandLineHelp();
                        Environment.Exit(1);
                        break;
                }
            }

            // Start log file
            AppLog appLog = new AppLog();
            ExceptionHandlers expHand = new ExceptionHandlers(appLog.SwLog);
            appLog.WriteLine(string.Format("START TS Automation : {0}\r\n", DateTime.Now.ToLongDateString()));

            #endregion

            #region Init EPM 2007 web services
            SPSite site = null;
            try
            {
                site = new SPSite(siteID);
            }
            catch (Exception ex)
            {
                appLog.WriteLine(string.Format("!!! Could not create SPSite: {0} for Site ID, application terminated : {1} \r\n", siteID, ex.Message));
                appLog.Close();
                return;
            }

            // Login
            loginWindows.Url = WSUrl(site, sspName, "loginwindows", false);
            loginWindows.Credentials = CredentialCache.DefaultCredentials;

            bool logonSucceeded = false;
            try
            {
                logonSucceeded = Program.loginWindows.Login();
            }
            catch { }

            if (!logonSucceeded)
            {
                appLog.WriteLine(string.Format("!!! Login did not succeed for url : {0} \r\n", Program.loginWindows.Url));
                appLog.Close();
                return;
            }

            admin.UseDefaultCredentials = true;
            admin.Url = WSUrl(site, sspName, "admin", false);
            customfields.UseDefaultCredentials = true;
            customfields.Url = WSUrl(site, sspName, "customfields", false);
            project.UseDefaultCredentials = true;
            project.Url = WSUrl(site, sspName, "project", false);
            queueSystem.UseDefaultCredentials = true;
            queueSystem.Url = WSUrl(site, sspName, "queuesystem", false);
            resource.UseDefaultCredentials = true;
            resource.Url = WSUrl(site, sspName, "resource", false);
            statusing.UseDefaultCredentials = true;
            statusing.Url = WSUrl(site, sspName, "statusing", true);
            statusingDER.UseDefaultCredentials = true;
            statusingDER.Url = statusing.Url;
            timeSheet.UseDefaultCredentials = true;
            timeSheet.Url = WSUrl(site, sspName, "timesheet", false);

            #endregion

            #region Init local variables

            Guid jobUid;
            Guid periodUid;
            Guid resUid;
            Guid tsmgrUid;
            Guid pmUid;
            Guid cfUidTS;
            Guid cfUidST;
            Random randObj;
            int nbOfSubTS = 0;
            int totalNbOfSubTS = 0;
            int totalNbOfFailedTS = 0;
            int totalNbOfFailedApprovals = 0;

            Stopwatch timeToProcessAll = new Stopwatch();
            timeToProcessAll.Start();
            Stopwatch timeToProcess = new Stopwatch();
            timeToProcess.Start();
            Stopwatch timeToSubmit = new Stopwatch();
            Stopwatch timeToApprove = new Stopwatch();

            resUid = PSIResource.GetResourceGuid(resourceName);
            tsmgrUid = PSIResource.GetResourceGuid(timesheetManagerName);
            pmUid = PSIResource.GetResourceGuid(projectManagerName);
            cfUidTS = PSICustomFields.GetCustomFieldUID(resourceCFTS);
            cfUidST = PSICustomFields.GetCustomFieldUID(resourceCFST);

            //statusingDER.Url = GeneralHelper.SetPortforImpersonation(projectServerURL, sspPort) + sspName + "/PSI/Statusing.asmx";

            if (resUid != Guid.Empty && purgeAllTSForAllRes)
            {
                purgeAllTSForAllRes = false;
                purgeAllTSForOneRes = true;
            }

            bool requestInitialOwnership = true;
            bool mutexWasCreated;
            Mutex mut; 

            #endregion

            #region Write Parameters
            appLog.WriteLine("-------- Start Application Parameters --------", false);
            appLog.WriteLine(string.Format("URL: {0}", projectServerURL), false);
            appLog.WriteLine(string.Format("Published DB SQL connection string: {0}", connectionString), false);
            appLog.WriteLine(string.Format("Use Earliest Period: {0}", useEarliestPeriod.ToString()), false);
            appLog.WriteLine(string.Format("Period to create: {0}", periodName), false);
            appLog.WriteLine(string.Format("Number of TS to create per Resource: {0}", nbTSToCreate.ToString()), false);
            appLog.WriteLine(string.Format("Number of Resource that will submit TS: {0}", nbResource.ToString()), false);
            appLog.WriteLine(string.Format("Start Date: {0}; Finish Date:  {1}", startDate.ToShortDateString(), finishDate.ToShortDateString()), false);
            appLog.WriteLine(string.Format("Timesheet Manager Name: {0}; Manager UID: {1}", timesheetManagerName, tsmgrUid.ToString()), false);
            appLog.WriteLine(string.Format("Purge all TS for all Resources: {0} - Purge all TS for {1}: {2}", purgeAllTSForAllRes, resourceName, purgeAllTSForOneRes), false);
            appLog.WriteLine(string.Format("Reset all Resources CF Flag: {0} for {1} and {2}", resetCFForAllRes, resourceCFTS, resourceCFST), false);
            appLog.WriteLine(string.Format("Create TS: {0}; Randomize timesheet status: {1}", createTS, randomizeTSStatus), false);
            appLog.WriteLine(string.Format("Task Remaining Hours Bag: {0}", taskRemaingHoursBag), false);
            appLog.WriteLine(string.Format("Import TS into MyTasks: {0}", importTS), false);
            appLog.WriteLine(string.Format("Approve All Task Update: {0}", approveTaskUpdate), false);
            appLog.WriteLine(string.Format("Debug Default Resource: {0}", debugDefaultRes), false);
            appLog.WriteLine(string.Format("Debug Default Project Manager: {0}", debugDefaultPM), false);
            appLog.WriteLine(string.Format("Wait for queue: {0}", waitForQueue), false);
            appLog.WriteLine(string.Format("Write detail log: {0}", detailLog), false);
            appLog.WriteLine(string.Format("Impersonation params: PWA Site ID (from WSS): {0}, SSP Name: {1}, SSP Port #: {2}", siteID, sspName, sspPort), false);
            appLog.WriteLine("-------- End Application Parameters --------\r\n", false);
            #endregion
        
            #region Initialise Enviroment
            // Purge all my jobs
            PSIQueueSystem.ClearAllMyFailedTSJobs();

            // Delete all TS for all Resources
            if (purgeAllTSForAllRes)
            {
                appLog.WriteLine("-------- Delete All TimeSheets for All Resources --------");
                int nbTSDeleted = PSITimeSheet.DeleteAllTSForAllRes(startDate, finishDate, appLog.SwLog, connectionString, Guid.Empty, string.Empty);
                appLog.WriteLine(string.Format("\r\n*** Execution Time to Delete {0} TimeSheets for all Resources : {1} (s) - {2} \r\n", nbTSDeleted, timeToProcess.Elapsed.TotalSeconds.ToString("N"), timeToProcess.Elapsed.ToString()), false);
                timeToProcess.Reset();
                timeToProcess.Start();
            }

            // Delete all TS for default resource
            if (purgeAllTSForOneRes)
            {
                appLog.WriteLine(string.Format("-------- Delete All TimeSheets for default resource: {0}--------", resourceName));
                int nbTSDeleted = PSITimeSheet.DeleteAllTSForOneRes(resUid, startDate, finishDate, appLog.SwLog, connectionString);

                appLog.WriteLine(string.Format("\r\n*** Execution Time to Delete {0} TimeSheets for all Resources : {1} (s) - {2} \r\n", nbTSDeleted, timeToProcess.Elapsed.TotalSeconds.ToString("N"), timeToProcess.Elapsed.ToString()), false);
                timeToProcess.Reset();
                timeToProcess.Start();
            }
            
            // Update all resource custom field -> set it to false, run this only once
            if (resetCFForAllRes)
            {
                // Reset Resource CF flag that tracks timesheet creation
                if (cfUidTS != Guid.Empty)
                {
                    appLog.WriteLine(string.Format("-------- Reset Resource CF flag '{0}' to false--------", resourceCFTS));
                    PSIResource.UpdateAllResourcesCustomField(cfUidTS, false, appLog);
                    appLog.WriteLine(string.Format("\r\n*** Execution Time to Update all Resources : {0} (s) - {1} \r\n", timeToProcess.Elapsed.TotalSeconds.ToString("N"), timeToProcess.Elapsed.ToString()), false);
                    timeToProcess.Reset();
                    timeToProcess.Start();
                }
                // Reset Resource CF flag that tracks statusing approval
                if (cfUidST != Guid.Empty)
                {
                    appLog.WriteLine(string.Format("-------- Reset Resource CF flag '{0}' to false--------", resourceCFST));
                    PSIResource.UpdateAllResourcesCustomField(cfUidST, false, appLog);
                    appLog.WriteLine(string.Format("\r\n*** Execution Time to Update all Resources : {0} (s) - {1} \r\n", timeToProcess.Elapsed.TotalSeconds.ToString("N"), timeToProcess.Elapsed.ToString()), false);
                    timeToProcess.Reset();
                    timeToProcess.Start();
                }
            }

            if (!createTS && !importTS)
                nbResource = 0;
            else
            {
                appLog.WriteLine(string.Format("-------- Loop over {0} resources that will submit {1} TimeSheet(s) each --------\r\n", nbResource, nbTSToCreate));
            }
            ResourceWS.ResourceDataSet resDS = new ResourceWS.ResourceDataSet();
            ResourceWS.ResourceDataSet pmDS = new ResourceWS.ResourceDataSet();
            ResourceWS.ResourceDataSet resDSCF = new ResourceWS.ResourceDataSet();

            if(!debugDefaultRes)
                resDS = PSIResource.GetResourceDataSet(true, false);
            else
                resDS = PSIResource.GetResourceDataSet(resUid);

            // Reset default resource's CF  
            if (purgeAllTSForOneRes)
                PSIResource.UpdateResourceCustomField(resDS, cfUidTS, false);

            if (resDS.Resources.Count < nbResource)
                nbResource = resDS.Resources.Count;

            if (!debugDefaultPM)
                pmDS = resDS; //PSIResource.GetResourceDataSet(true, false);
            else
                pmDS = PSIResource.GetResourceDataSet(pmUid);

            if (pmDS.Resources.Count < nbPM)
                nbPM = pmDS.Resources.Count;

            if (periodName != "*")
                nbTSToCreate = 1;

            AdminWS.TimePeriodDataSet timePeriodDs = PSIAdmin.ReadOpenPeriods();
            List<string> resListUpdated = new List<string>();
            List<string> resListNotUpdated = new List<string>();
            List<string> pmListUpdated = new List<string>();
            List<string> pmListNotUpdated = new List<string>();
            List<string> pmListStatusApproved = new List<string>();

            #endregion

            #region Report Time and Progress

            randObj = new Random(DateTime.Now.Millisecond);

            timeToSubmit.Start();

            // Loop over all Resources that will submit TimeSheet(s)
            for (int r = 0, z = 0; r < nbResource && z < resDS.Resources.Count; r++)
            {
                // Choose random resource from list
                int resIndex = randObj.Next(resDS.Resources.Count);

                resUid = resDS.Resources[resIndex].RES_UID;
                resourceName = resDS.Resources[resIndex].RES_NAME;

                // Lock further updates to a single resource by updating specific CF flag to true
                if (cfUidTS != Guid.Empty)
                {
                    // Wait in case the resource is being modified by a different instance
                    mut = new Mutex(requestInitialOwnership, "AutomateTSMutex_TS" + resourceName, out mutexWasCreated);
                    if (!(requestInitialOwnership && mutexWasCreated))
                    {
                        mut.WaitOne();
                    }

                    // Get latest and greatest :)
                    resDSCF = PSIResource.GetResourceDataSet(resUid);
                    // Check if resource has been updated already
                    if (PSIResource.GetCFFlagValue(resDSCF, cfUidTS, resUid))
                    {
                        if (!resListNotUpdated.Contains(resourceName))
                        {
                            resListNotUpdated.Add(resourceName);
                            z++;
                        }
                        r--;
                        mut.ReleaseMutex();
                        continue;
                    }
                    PSIResource.UpdateResourceCustomField(resDSCF, cfUidTS, true);
                    resListUpdated.Add(resourceName);
                    mut.ReleaseMutex();
                }

                appLog.WriteLine(string.Format("{0}/{1} - Resource Name: {2}; Resource UID: {3}\r\n", r, nbResource, resourceName, resUid.ToString()), false);
                appLog.WriteLine(string.Format("-------- Loop to create {0} TimeSheet(s) for {1} --------\r\n", nbTSToCreate, resourceName));

                nbTSToCreate = nbTSToCreate > timePeriodDs.TimePeriods.Count ? timePeriodDs.TimePeriods.Count : nbTSToCreate;
                // Loop to create all TimeSheets per Resource
                for (int j = 0; j < nbTSToCreate; j++)
                {
                    try
                    {
                        // Get TimeSheet period ID
                        if (periodName != "*")
                            periodUid = PSIAdmin.GetTSPeriodUid(periodName);
                        else if (useEarliestPeriod)
                            periodUid = PSITimeSheet.FindFirstEmptyPeriod(resUid, timePeriodDs);
                        else
                            periodUid = PSITimeSheet.FindFirstEmptyPeriod(resUid, timePeriodDs, DateTime.Now);

                        if (periodUid == Guid.Empty)
                            continue;

                        List<Guid> assignments = new List<Guid>();
                        Guid timeSheetUid = Guid.Empty;
                        TimeSheetWS.TimesheetDataSet timesheetDs = PSITimeSheet.ReadTimesheetByPeriod(resUid, periodUid);

                        #region Create TimeSheet
                        // Check if TS already exists and is empty
                        if (createTS && timesheetDs.Lines.Count == 0)
                        {
                            timeSheetUid = PSITimeSheet.CreateTimeSheetFor(resUid, tsmgrUid, periodUid, j + 1);

                            appLog.WriteLine(string.Format("Created TimeSheet: {0}", timeSheetUid), false);

                            if (timeSheetUid == Guid.Empty)
                                continue;

                            timesheetDs = Program.timeSheet.ReadTimesheet(timeSheetUid);

                            List<Guid> tsLines = new List<Guid>();
                            List<Guid> adminLines = new List<Guid>();

                            for (int i = 0; i < timesheetDs.Lines.Count; i++)
                            {
                                if (timesheetDs.Lines[i].TS_LINE_VALIDATION_TYPE == (int)PSLibrary.TimesheetEnum.ValidationType.Verified)
                                {
                                    assignments.Add(timesheetDs.Lines[i].ASSN_UID);
                                    tsLines.Add(timesheetDs.Lines[i].TS_LINE_UID);
                                }
                                else // Admin time (Unverified)
                                    adminLines.Add(timesheetDs.Lines[i].TS_LINE_UID);
                            }

                            PSITimeSheet.AddActualsRows(timesheetDs, timePeriodDs, adminLines, tsLines, periodUid, appLog, taskRemaingHoursBag);

                            // Update TS
                            jobUid = Guid.NewGuid();
                            Program.timeSheet.QueueUpdateTimesheet(jobUid, timeSheetUid, timesheetDs);
                            if (waitForQueue)
                                PSIQueueSystem.WaitForQueue(Program.queueSystem, jobUid);

                            appLog.WriteLine(string.Format("Updated {0}", timesheetDs.Headers[0].TS_NAME), false);

                            // Submit TS
                            bool updateTS = true;
                            if (randomizeTSStatus)
                                updateTS = GeneralHelper.GenerateRandomBoolean();

                            if (updateTS)
                            {
                                jobUid = Guid.NewGuid();
                                Program.timeSheet.QueueSubmitTimesheet(jobUid, timeSheetUid, tsmgrUid, "Submited via PSI");
                                if (waitForQueue)
                                    PSIQueueSystem.WaitForQueue(Program.queueSystem, jobUid);

                                appLog.WriteLine(string.Format("Submitted {0}", timesheetDs.Headers[0].TS_NAME), false);
                            }


                           // Approve TS
                            if (randomizeTSStatus && updateTS)
                                updateTS = GeneralHelper.GenerateRandomBoolean();

                           if (approveTS && updateTS)
                           {
                                jobUid = Guid.NewGuid();
                                Program.timeSheet.QueueReviewTimesheet(jobUid, timeSheetUid, tsmgrUid, "Approved via PSI", MicrosoftFrance.MCS.EPM2007.TimeSheetWS.Action.Approve);
                                if (waitForQueue)
                                    PSIQueueSystem.WaitForQueue(Program.queueSystem, jobUid);

                                appLog.WriteLine(string.Format("Approved {0} by Manager {1}\r\n", timesheetDs.Headers[0].TS_NAME, tsmgrUid), false);
                            }

                            nbOfSubTS++;

                            GeneralHelper.Wait(waitTimesheets);
                        }

                        #endregion

                        #region Import TimeSheets & do Statusing
                        if (importTS && PSIStatusing.ContainsStatusUpdate(timesheetDs))
                        {
                            timesheetDs = PSITimeSheet.ReadTimesheetByPeriod(resUid, periodUid);

                            // Step 0 - Setup Impersonation
                            StatusingDerived.SetImpersonationContext(resDS.Resources[resIndex].RES_IS_WINDOWS_USER, resDS.Resources[resIndex].WRES_ACCOUNT, resUid, Guid.NewGuid(), siteID, "1033");

                            // Step 1 - Import TimeSheet
                            PSIStatusing.ImportTimeSheet(periodUid, detailLog, appLog);
                            appLog.WriteLine(string.Format("Imported TimeSheet {0} for Period {1} - {2}", timeSheetUid, periodName, periodUid));

                            // Step 2 - Update Status
                            if (timesheetDs.Lines.Count > 0)
                            {
                                PSIStatusing.UpdateStatus(timesheetDs, detailLog, appLog);
                                appLog.WriteLine(string.Format("Update My Task Status"));

                                PSIStatusing.SubmitStatus(timesheetDs, detailLog, appLog);
                                appLog.WriteLine(string.Format("Submit Status Update"));
                            }
                            
                            GeneralHelper.Wait(waitTasks);
                        }
                        #endregion
                    }
                    catch (SoapException ex)
                    {
                        expHand.HandleSoapException(ex);
                        PSLibrary.PSClientError psCE = new PSLibrary.PSClientError(ex);
                        totalNbOfFailedTS++;
                    }
                    catch (WebException ex)
                    {
                        expHand.HandleWebException(ex);
                        totalNbOfFailedTS++;
                    }
                    catch (ConstraintException ex)
                    {
                        appLog.WriteLine(string.Format("ConstraintException : {0}", ex.Message), false);
                        totalNbOfFailedTS++;
                    }
                    catch (Exception ex)
                    {
                        expHand.HandleException(ex);
                        totalNbOfFailedTS++;
                    }

                } // TS period loop

                appLog.WriteLine(string.Format("\r\n{0} TimeSheet were created for {1} \r\n", nbOfSubTS, resourceName), false);
                appLog.WriteLine(string.Format("*** Execution Time to Submit all TimeSheets: {0} (s) - {1} \r\n", timeToProcess.Elapsed.TotalSeconds.ToString("N"), timeToProcess.Elapsed.ToString()), false);
                timeToProcess.Reset();
                timeToProcess.Start();

                totalNbOfSubTS = totalNbOfSubTS + nbOfSubTS;
                nbOfSubTS = 0;

            } // TS resource loop

            timeToSubmit.Stop();

            #endregion

            #region Approve Statusing
            // Approve all task update (-> will republish projects)
            int statusApproved = 0;
            if (approveTaskUpdate)
            {
                try
                {
                    appLog.WriteLine(string.Format("-------- Loop over {0} 'Project Managers' to Approve all their status uptades --------", nbPM),false);
                    
                    randObj = new Random(DateTime.Now.Millisecond);
                    timeToApprove.Start();

                    // Loop over all resources that need to approve status update
                    for (int r = 0, z = 0; r < nbPM && z < pmDS.Resources.Count; r++)
                    {
                        // Choose random resource from list
                        int resIndex = randObj.Next(pmDS.Resources.Count);

                        if (!pmDS.Resources[resIndex].IsWRES_ACCOUNTNull())
                        {
                            resUid = pmDS.Resources[resIndex].RES_UID;
                            resourceName = pmDS.Resources[resIndex].RES_NAME;
                            appLog.WriteLine(string.Format("-------- Use {0} 'Project Managers' to Approve all their status updates --------", resourceName), false);

                            // Lock further updates to a single resource by updating specific CF flag to true
                            if (cfUidST != Guid.Empty)
                            {
                                // Wait in case the resource is being modified by a different instance
                                mut = new Mutex(requestInitialOwnership, "AutomateTSMutex_ST" + resourceName, out mutexWasCreated);
                                if (!(requestInitialOwnership && mutexWasCreated))
                                {
                                    mut.WaitOne();
                                }

                                // Get latest and greatest :)
                                resDSCF = PSIResource.GetResourceDataSet(resUid);
                                // Check if resource flag has been updated already
                                if (PSIResource.GetCFFlagValue(resDSCF, cfUidST, resUid))
                                {
                                    if (!pmListNotUpdated.Contains(resourceName))
                                    {
                                        pmListNotUpdated.Add(resourceName);
                                        z++;
                                    }
                                    mut.ReleaseMutex();
                                    r--;
                                    appLog.WriteLine(string.Format("-------- Don't use {0} 'Project Managers'  --------", resourceName), false);
                                    continue;
                                }

                                PSIResource.UpdateResourceCustomField(resDSCF, cfUidST, true);
                                pmListUpdated.Add(resourceName);
                                mut.ReleaseMutex();
                            }

                            // Setup impersonation
                            StatusingDerived.SetImpersonationContext(pmDS.Resources[resIndex].RES_IS_WINDOWS_USER, pmDS.Resources[resIndex].WRES_ACCOUNT, resUid, Guid.NewGuid(), siteID, "1033");
                            
                            // Get assignments waiting for approval
                            StatusingWS.StatusApprovalDataSet statusApprovalDs = Program.statusingDER.ReadStatusApprovalsSubmitted(false);

                            if (statusApprovalDs.StatusApprovals.Count > 0)
                            {
                                appLog.WriteLine(string.Format("Starting Approvals from {0}\r\n", resourceName), false);
                                appLog.WriteLine(string.Format("---------------------------------------------------- \r\n", resourceName), false);

                                try
                                {
                                    statusApproved = statusApproved + PSIStatusing.UpdateAndApplyStatusApprovals(statusApprovalDs, resUid, detailLog, appLog);
                                    GeneralHelper.Wait(waitApprovals);
                                }
                                catch (SoapException ex)
                                {
                                    expHand.HandleSoapException(ex);
                                    totalNbOfFailedApprovals++;
                                }
                                catch (WebException ex)
                                {
                                    expHand.HandleWebException(ex);
                                    totalNbOfFailedApprovals++;
                                }
                                catch (ConstraintException ex)
                                {
                                    appLog.WriteLine(string.Format("ConstraintException : {0}", ex.Message), false);
                                    totalNbOfFailedApprovals++;
                                }
                                catch (Exception ex)
                                {
                                    expHand.HandleException(ex);
                                    totalNbOfFailedApprovals++;
                                }
                                pmListStatusApproved.Add(resourceName);

                            }
                            else
                            {
                                appLog.WriteLine(string.Format("No Approvals from {0}\r\n", resourceName), false);
                                r--;
                            }
                        }
                    }

                    timeToApprove.Stop();
        
                    appLog.WriteLine(string.Format("*** Updated and Applied {0} Status Approvals \r\n", statusApproved), false);
                }
                catch (Exception ex)
                {
                    expHand.HandleException(ex);
                }
            }
            #endregion

            #region Write Output Summary
            appLog.WriteLine(string.Format("Total TS Submitted: {0}, Total Failed TS: {1}, Total TS To Create per Resource: {2}\r\n", totalNbOfSubTS, totalNbOfFailedTS, nbTSToCreate), false);
            appLog.WriteLine(string.Format("*** Execution Time to Submit all TimeSheets for all Resources : {0} (s) - {1}", timeToSubmit.Elapsed.TotalSeconds.ToString("N"), timeToSubmit.Elapsed.ToString()), false);
            double avgTSCreationTime = timeToSubmit.Elapsed.TotalSeconds / totalNbOfSubTS;
            appLog.WriteLine(string.Format("*** Average Execution Time to Submit One TimeSheet : {0} (s)", avgTSCreationTime.ToString("N")), false);

            appLog.WriteLine(string.Format("Total Status Updates Approved: {0}, Total Failed TS: {1}\r\n", statusApproved, totalNbOfFailedApprovals), false);
            appLog.WriteLine(string.Format("*** Execution Time to Approve all Updates for all Project Managers : {0} (s) - {1}", timeToApprove.Elapsed.TotalSeconds.ToString("N"), timeToApprove.Elapsed.ToString()), false);
            double avgApprovalTime = timeToApprove.Elapsed.TotalSeconds / statusApproved;
            appLog.WriteLine(string.Format("*** Average Execution Time to Approve One Update : {0} (s)", avgApprovalTime.ToString("N")), false);

            timeToProcess.Stop();
            timeToProcessAll.Stop();
            appLog.WriteLine(string.Format("\r\n*** Total Execution Time : {0} (s) - {1} \r\n", timeToProcessAll.Elapsed.TotalSeconds.ToString("N"), timeToProcessAll.Elapsed.ToString()), false);

            if (resListUpdated.Count > 0)
            {
                resListUpdated.Sort();
                appLog.WriteLine(string.Format("\r\n-------- List of {0} Resources Updated --------", resListUpdated.Count), false);
                for (int r = 0; r < resListUpdated.Count; r++)
                {
                    appLog.WriteLine(string.Format("{0}", resListUpdated[r]), false);
                }
            }

            if (resListNotUpdated.Count > 0)
            {
                resListNotUpdated.Sort();
                appLog.WriteLine(string.Format("\r\n-------- List of {0} Resources NOT Updated (skipped) --------", resListNotUpdated.Count), false);
                for (int r = 0; r < resListNotUpdated.Count; r++)
                {
                    appLog.WriteLine(string.Format("{0}", resListNotUpdated[r]), false);
                }
            }

            if (pmListStatusApproved.Count > 0)
            {
                pmListStatusApproved.Sort();
                appLog.WriteLine(string.Format("\r\n-------- List of {0} 'Project Managers' Approving Status --------", pmListStatusApproved.Count), false);
                for (int r = 0; r < pmListStatusApproved.Count; r++)
                {
                    appLog.WriteLine(string.Format("{0}", pmListStatusApproved[r]), false);
                }
            }

            appLog.WriteLine("\r\n", false);
            appLog.WriteLine(string.Format("END TS Automation : {0}\r\n", DateTime.Now.ToLongDateString()));
            appLog.Close();

            // Write test statistics in a summary log file
            AppLog appLogSummary = new AppLog(summaryFileLog);
            if(purgeAllTSForAllRes)
                appLogSummary.WriteLine("MachineName,Date,Time,LogFile,TotalTime,TSTime,APTime,SubmittedTS,ResUpdated,ResNotUpdated,PMStatusApproved", false, false);
  
            appLogSummary.WriteLine(string.Format("{0},{1},{2},{3},{4},{5},{6},{7},{8},{9},{10}",
                Environment.MachineName,
                DateTime.Now.ToShortDateString(), DateTime.Now.ToLongTimeString(),
                appLog.FileName,
                timeToProcessAll.Elapsed.TotalSeconds.ToString("N"),
                timeToSubmit.Elapsed.TotalSeconds.ToString("N"),
                timeToApprove.Elapsed.TotalSeconds.ToString("N"),
                totalNbOfSubTS, resListUpdated.Count, resListNotUpdated.Count, 
                pmListStatusApproved.Count), false, false);

            appLogSummary.Close();
            #endregion
        }

        // Construct URL of PSI web services
        private static string WSUrl(SPSite ss, string sspName, string wsName, bool impersonate)
        {
            if (impersonate)
                return string.Format("{0}//{1}:56737/{2}/psi/{3}.asmx",
                    ss.Protocol, ss.HostName, sspName, wsName);
            else
                return string.Format("{0}/_vti_bin/psi/{1}.asmx",
                    ss.Url, wsName);
        }

        private static void WriteCommandLineHelp()
        {
            string help = @"
Automate TimeSheet submission and Task Updates (statusing).

AUTOMATETS [/p period] [/r resources_number] [/t timesheets_number]
    [/ct] [/at][/wat wait_after_timesheeting]
    [/it] [/wat wait_after_statusing]
    [/pmn projectmangers_number]
    [/as] [/waa wait_after_approving]
    [/dr] [/rn resource_name]
    [/tmn timesheetManager_name]
    [/dpm] [/pmn resource_name]
    [/pts] [/rcf] [/rc]
    [/sd start_date]
    [/fd finish_date]
    [/trhb hours]
    [/rtsst]

    /p    Specific period used to create TimeSheet. None by default.

    /r    Number of resources to update. 1 by default.

    /t    Number of timesheets to create per resource. 1 by default.

    /ct   Create, Update, and Submit TimeSheets. 'false' by default.

    /at   Approve TimeSheets. 'false' by default.

    /wat  Waiting time after timesheet submition (miliseconds). '0' by default

    /it   Import TimeSheets in 'My Tasks' and submit update. 'false' by default.

    /was  Wating time after tasks apdates submition (miliseconds). '0' by default

    /pm   Number of project managers to use. 1 by default.

    /as   Approved Task Status Updates. 'false' by default.

    /waa  Wating time after approve status updates (miliseconds). '0' by default

    /dr   Debug default resource. 'false' by default.

    /dpm  Debug default project manager. 'false' by default.

    /pts  Purge all TimeSheets for all resources !!!. 'false' by default.

    /rcf  Reset resource custom field flag to false for all resources !!!. 'false' by default.

    /rc  Reset resource custom field flag to false for current resource (used with /rn)!!!. 'false' by default.

    /rn   Resource Name. Empty by default. Should be used with /dr

    /tmn TimesheetManager. Empty by default, Should be used with /dr

    /pmn  Project Manager Name. Empty by default. Should be used with /dpm

    /sd   Start date to purge TS, format is yyyyMMdd. 2007/01/01 by default

    /fd   Finish date to purge TS, format is yyyyMMdd. 2007/03/01 by default
    
    /trhb Task remaining hour bag, number of hours spread randomly accross all tasks, 1920000 (32hrs) by default, for instance enter 480000
 for 8h

    /rtsst Randomize timesheet approvals
";
            Console.Write(help);
        }
    }
}